package com.gwnet.cabinet.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.lock.annotation.Lock4j;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.gwnet.cabinet.domian.Cabinet;
import com.gwnet.cabinet.domian.CabinetBox;
import com.gwnet.cabinet.domian.CabinetBoxLog;
import com.gwnet.cabinet.domian.base.Result;
import com.gwnet.cabinet.domian.bo.*;
import com.gwnet.cabinet.domian.vo.OpenBoxVO;
import com.gwnet.cabinet.domian.vo.TakeBoxVO;
import com.gwnet.cabinet.enums.*;
import com.gwnet.cabinet.mapper.CabinetBoxLogMapper;
import com.gwnet.cabinet.mapper.CabinetBoxMapper;
import com.gwnet.cabinet.mapper.CabinetMapper;
import com.gwnet.cabinet.service.ApiService;
import com.gwnet.cabinet.service.CabinetBoxLogService;
import com.gwnet.cabinet.service.CabinetBoxService;
import com.gwnet.common.core.exception.base.BaseException;
import com.gwnet.shop.domain.OrderDelivery;
import com.gwnet.shop.domain.OrderInfo;
import com.gwnet.shop.enums.DevStatusEnum;
import com.gwnet.shop.enums.OrderStatusEnum;
import com.gwnet.shop.service.IOrderDeliveryService;
import com.gwnet.shop.service.IOrderInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * <p>
 * 外卖柜-箱格表 服务实现类
 * </p>
 *
 * @author WangLiang
 * @since 2024-07-23
 */
@Slf4j
@Service
public class CabinetBoxServiceImpl extends ServiceImpl<CabinetBoxMapper, CabinetBox> implements CabinetBoxService {

    /** 超时默认标准 */
    private final static int STANDARD_MINUTE = 40;

    private final ApiService apiService;
    private final CabinetMapper cabinetMapper;
    private final CabinetBoxMapper cabinetBoxMapper;
    private final CabinetBoxLogMapper cabinetBoxLogMapper;
    private final CabinetBoxLogService cabinetBoxLogService;
    private final IOrderInfoService orderInfoService;
    private final IOrderDeliveryService deliveryService;

    @Autowired
    public CabinetBoxServiceImpl(ApiService apiService, CabinetMapper cabinetMapper, CabinetBoxMapper cabinetBoxMapper, CabinetBoxLogMapper cabinetBoxLogMapper, CabinetBoxLogService cabinetBoxLogService, IOrderInfoService orderInfoService, IOrderDeliveryService deliveryService) {
        this.apiService = apiService;
        this.cabinetMapper = cabinetMapper;
        this.cabinetBoxMapper = cabinetBoxMapper;
        this.cabinetBoxLogMapper = cabinetBoxLogMapper;
        this.cabinetBoxLogService = cabinetBoxLogService;
        this.orderInfoService = orderInfoService;
        this.deliveryService = deliveryService;
    }


    @Lock4j(keys = {"#openBO.getDevSn()"})
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean putToBox(CabinetBoxOpenBO openBO) {
        // 根据订单ID查询出订单信息 1.判断订单是符合开柜条件
        OrderInfo orderInfo = orderInfoService.getById(openBO.getOrderId());
        // 待收货状态下证明人已经把外卖放进去了， 不能一个单子开两个门
        if(OrderStatusEnum.WAIT_TAKE_DEV.code().equals(orderInfo.getOrderStatus())){
            throw new BaseException("该订单已经存放至外卖柜子，请勿重复开门");
        }


        // 查询该订单是否有存放记录，有存放记录就不给开门，不能重复存放
        LambdaQueryWrapper<CabinetBoxLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CabinetBoxLog::getOrderId,openBO.getOrderId());
        List<CabinetBoxLog> boxLogs = cabinetBoxLogMapper.selectList(queryWrapper);
        if(boxLogs.size() > 0){
            // 这里应该抛异常做响应处理
            return false;
        }

        // 箱格信息
        CabinetBox cabinetBox = getBox(openBO.getDevSn(),openBO.getBoxNo());

        OpenBoxBO params = new OpenBoxBO();
        params.setDev_sn(openBO.getDevSn());
        // 开箱类型 1分配开箱 2分配但不开箱(目前不考虑占柜)
        params.setOpen_type(OpenTypeEnum.ALLOCATION_OPEN.code());
        params.setBoxCh(openBO.getBoxNo());
        // 用户凭证类型 目前用验证码
        params.setCertType(CertTypeEnum.OPEN_PWD.code());
        // 验证码生成
        String code = RandomUtil.randomNumbers(4);
        log.info("打印订单号：{}，验证码为：{}",openBO.getOrderId(),code);
        params.setOpen_pwd(code);
        // 是否加热 0 否 1 是
        params.setIs_heat(openBO.getIsHeat());
        // 时间戳
        params.setDateTime(System.currentTimeMillis());




        // 设置箱格订单号，状态
        cabinetBox.setOrderId(openBO.getOrderId());
        cabinetBox.setUseState(UseStatusEnum.USED.code());
        cabinetBoxMapper.updateById(cabinetBox);

        // 设置外卖柜整体情况
        updateCabinetState(openBO.getDevSn(),-1,1);

        // 修改订单取餐号，修改devSn码
        orderInfo.setOrderStatus(OrderStatusEnum.WAIT_TAKE_DEV.code());
        orderInfo.setDevSn(openBO.getDevSn());
        orderInfo.setBoxNo(openBO.getBoxNo());
        orderInfo.setFoodDeliveryCode(params.getOpen_pwd());
        orderInfoService.updateById(orderInfo);

        // 调用开箱存货
        OpenBoxVO openBoxVO = apiService.openBox(params);

        // 保存日志
        Boolean flag = saveBoxLog(openBO.getOrderId(),params.getDev_sn(),params.getOpen_pwd(), openBoxVO, cabinetBox, UseTypeEnum.PUT.code());
        return flag;
    }

    @Lock4j(keys = {"#takeBoxBO.getDevSn()"})
    @Override
    public Boolean getToBox(CabinetTakeBoxBO takeBoxBO) {

        // 根据订单ID和编码凭证查询入柜历史
        LambdaQueryWrapper<CabinetBoxLog> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CabinetBoxLog::getOrderId,takeBoxBO.getOrderId())
                .eq(CabinetBoxLog::getCode,takeBoxBO.getOpenPwd())
                .orderByDesc(CabinetBoxLog::getCreateTime);

        List<CabinetBoxLog> list = cabinetBoxLogMapper.selectList(queryWrapper);
        if(list.size() == 0){
            // 抛出异常，取件码不正确
            throw new BaseException("取件码不正确!");
        }
        CabinetBoxLog boxLog = list.get(0);

        // 根据日志查询箱子信息
        CabinetBox cabinetBox = getBox(boxLog.getDevSn(), boxLog.getBoxNo());


        TakeBoxBO params = new TakeBoxBO();
        params.setDev_sn(takeBoxBO.getDevSn());
        // 开箱类型 1取出并结束，2中途使用，取出但不结束订单
        params.setOpen_type(OpenTypeEnum.TAKE_BOX_OPEN_END.code());
        // 箱门编号 按说不需要箱门编号
        params.setBoxCh(boxLog.getBoxNo());
        // 开箱密码凭证
        params.setOpen_pwd(takeBoxBO.getOpenPwd());
        // 用户类型凭证
        params.setCertType(CertTypeEnum.OPEN_PWD.code());
        params.setDateTime(System.currentTimeMillis());

        TakeBoxVO takeBoxVO = apiService.takeBox(params);

        // 设置箱格订单号，状态
        cabinetBox.setUseState(UseStatusEnum.IDLE.code());
        cabinetBoxMapper.cleanOrderId(cabinetBox);

        // 设置外卖柜整体情况
        updateCabinetState(takeBoxBO.getDevSn(),1,-1);

        // 保存日志
        Boolean flag = saveBoxLog(takeBoxBO.getOrderId(),params.getDev_sn(),params.getOpen_pwd(), takeBoxVO, cabinetBox, UseTypeEnum.GET.code());


        return flag;
    }

    @Lock4j(keys = {"#adminOpenBO.getDevSn()"})
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean adminOpen(AdminOpenBO adminOpenBO) {

        ManagerBoxesBO boxesBO = new ManagerBoxesBO();
        // 设备号
        boxesBO.setDev_sn(adminOpenBO.getDevSn());
        // 1：仅开箱，2：仅清箱，3：清箱并开箱，4：锁箱，5：解锁 9:打开箱门B面
        boxesBO.setOpen_type(adminOpenBO.getOpenType());
        // 允许传输多个，以“1，2，3，4”的方式
        boxesBO.setBoxChs(adminOpenBO.getBoxChs());
        // 时间戳
        boxesBO.setDateTime(System.currentTimeMillis());
        // 管理员
        Result result = apiService.managerBoxes(boxesBO);

        String[] boxNoArray = adminOpenBO.getBoxChs().split(",");

        List<CabinetBoxLog> saveList = new ArrayList<>();

        Map<Integer, CabinetBox> boxMap = getBoxMapByDevSn(adminOpenBO.getDevSn());

        for (String boxNo : boxNoArray) {
            CabinetBoxLog boxLog = new CabinetBoxLog();
            CabinetBox box = boxMap.get(Integer.parseInt(boxNo));
            // 后续要跟全局ID统一
            boxLog.setLogId(IdWorker.getId());
            // 根据管理员所在校区获取他的 school_id
            boxLog.setSchoolId(1L);
            boxLog.setDevSn(adminOpenBO.getDevSn());
            boxLog.setBoxId(box.getBoxId());
            // 使用类型
            boxLog.setUseType(UseTypeEnum.OPERATION.code());
            boxLog.setCreateTime(new Date());
            boxLog.setResponseData(JSONObject.toJSONString(result));
            boxLog.setBoxNo(Integer.parseInt(boxNo));
            saveList.add(boxLog);
        }


        // 批量保存日志
        cabinetBoxLogService.saveBatch(saveList);

        if(result.getCode().equals(ResultCodeEnum.SUCCESS)){
            return true;
        }

        return false;
    }


    /**
     * 校验箱格是否能够允许放入
     * @param cabinetBox
     * @return
     */
    private CabinetBox checkBox(CabinetBox cabinetBox){
        // TODO 一系列的校验手段，出现问题直接抛出异常，由全局异常捕捉处理并响应给前端



        return cabinetBox;
    }


    /**
     * 保存历史记录
     * @param orderId 订单ID，后续要改成查出来的订单实体
     * @param devSn  机器号
     * @param pwd   取件码
     * @param result 调用第三方的响应
     * @param cabinetBox 外卖柜盒子信息
     * @param useType   使用类型 1.放餐， 2.取餐 ，3.检修
     * @return
     */
    @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW)
    public Boolean saveBoxLog(Long orderId,String devSn,String pwd,
                               Object result,CabinetBox cabinetBox,
                               Integer useType){
        if(ObjectUtils.isNotEmpty(result)){
            CabinetBoxLog boxLog = new CabinetBoxLog();
            // 配置在外层
            boxLog.setLogId(IdWorker.getId());

            // 外卖柜ID
            boxLog.setDevSn(devSn);
            // 箱格ID
            boxLog.setBoxId(cabinetBox.getBoxId());
            // 箱格编号
            boxLog.setBoxNo(cabinetBox.getBoxNo());
            // 订单ID
            boxLog.setOrderId(orderId);
            // 学校ID，后续从订单信息中获取
            boxLog.setSchoolId(1L);
            // 使用类型
            boxLog.setUseType(useType);
            // 取件码
            boxLog.setCode(pwd);
            boxLog.setCreateTime(new Date());
            // 响应数据需要备份一份
            boxLog.setResponseData(JSONObject.toJSONString(result));
            return cabinetBoxLogMapper.insert(boxLog) > 0;
        }
        return false;
    }


    /**
     * 获取箱子实体
     * @param devSn 设备编号
     * @param boxNo 箱子编号
     * @return
     */
    private CabinetBox getBox(String devSn,Integer boxNo){
        // 查询箱柜信息，判断是否允许放入
        LambdaQueryWrapper<CabinetBox> boxWrapper = new LambdaQueryWrapper();
        boxWrapper.eq(CabinetBox::getDevSn,devSn)
                .eq(CabinetBox::getBoxNo,boxNo);

        List<CabinetBox> boxList = cabinetBoxMapper.selectList(boxWrapper);

        if(boxList.size() == 0){
            // 请传入正确的柜门和箱格信息
            throw new BaseException("请输入正确的柜门和箱格信息");
        }

        // 箱格信息
        CabinetBox cabinetBox = checkBox(boxList.get(0));
        return cabinetBox;
    }


    /**
     * 根据设备号获取设备下的箱格map
     * @param devSn 设备ID
     * @return
     */
    public Map<Integer,CabinetBox> getBoxMapByDevSn(String devSn){
        LambdaQueryWrapper<CabinetBox> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CabinetBox::getDevSn,devSn);
        List<CabinetBox> cabinetBoxes = cabinetBoxMapper.selectList(queryWrapper);
        // 转换为 Map<Integer, CabinetBox>
        Map<Integer, CabinetBox> cabinetBoxMap = cabinetBoxes.stream()
                .collect(Collectors.toMap(CabinetBox::getBoxNo, cabinetBox -> cabinetBox));
        return cabinetBoxMap;
    }


    /**
     * 查询超时外卖列表
     * @param timeOutBoxBO
     * @return
     */
    @Override
    public List<CabinetBoxLog> getTimeOutList(TimeOutBoxBO timeOutBoxBO){
        // 如果没有定义超时标准就用默认的超时规则
        if(ObjectUtils.isEmpty(timeOutBoxBO.getStandardMinute())){
            timeOutBoxBO.setStandardMinute(STANDARD_MINUTE);
        }
        List<CabinetBoxLog> timeOutList = cabinetBoxLogMapper.getTimeOutList(timeOutBoxBO);
        return timeOutList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean takeEvent(TakeEventBO eventBO) {
        log.info("打印取件回调参数：{}",eventBO);
        // 查询未取出的物品，并且物品的订单号
        CabinetBox box = getBox(eventBO.getDev_sn(), eventBO.getBoxCh());
        String orderId = String.valueOf(box.getOrderId());
        box.setUseState(UseStatusEnum.IDLE.code());
        cabinetBoxMapper.cleanOrderId(box);

        // 设置外卖柜整体情况
        updateCabinetState(eventBO.getDev_sn(),-1,1);

        // 保存取出记录
        Boolean flag = saveBoxLog(box.getOrderId(), eventBO.getDev_sn(), eventBO.getCert(), JSONObject.toJSONString(eventBO), box, UseTypeEnum.GET.code());


        // 更新对应的订单信息，即货物取已经取走
        OrderInfo orderInfo = orderInfoService.getById(orderId);
        orderInfo.setOrderStatus(OrderStatusEnum.SUCCESS.code());
        orderInfoService.updateById(orderInfo);

        OrderDelivery delivery = deliveryService.getById(orderInfo.getDeliveryId());
        delivery.setDeliveryStatus(DevStatusEnum.SUCCESS.code());
        deliveryService.updateById(delivery);

        log.info("回调并保存区间记录执行结束");
        return flag;
    }

    @Override
    public IPage<CabinetBox> page(CabinetBoxPageBO pageBO) {
        Page<CabinetBox> page = Page.of(pageBO.getCurrent(),pageBO.getSize());
        LambdaQueryWrapper<CabinetBox> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CabinetBox::getDevSn,pageBO.getDevSn());
        Page<CabinetBox> resultPage = cabinetBoxMapper.selectPage(page, queryWrapper);
        return resultPage;
    }

    @Override
    public List<CabinetBox> listByDevSn(CabinetBoxListBO boxListBO) {
        LambdaQueryWrapper<CabinetBox> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(CabinetBox::getDevSn,boxListBO.getDevSn());

        // 箱格类型(1小箱 2中箱 3大箱)
        if(ObjectUtils.isNotEmpty(boxListBO.getBoxType())){
           queryWrapper.eq(CabinetBox::getBoxType,boxListBO.getBoxType());
        }

        // 箱格状态(0 正常, 1 禁用/锁定)
        if(ObjectUtils.isNotEmpty(boxListBO.getBoxStatus())){
            queryWrapper.eq(CabinetBox::getBoxStatus,boxListBO.getBoxStatus());
        }
        // 使用状态(0 空闲, 1 使用中, 2 超时)
        if(ObjectUtils.isNotEmpty(boxListBO.getUseState())){
            queryWrapper.eq(CabinetBox::getUseState,boxListBO.getUseState());
        }
        List<CabinetBox> boxList = cabinetBoxMapper.selectList(queryWrapper);
        return boxList;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean updateState(CabinetBoxUpdateBO updateBO) {
        // 旧箱格数据
        CabinetBox oldBox = cabinetBoxMapper.selectById(updateBO.getBoxId());

        CabinetBox cabinetBox = new CabinetBox();
        BeanUtils.copyProperties(updateBO,cabinetBox);
        int boxCount = cabinetBoxMapper.updateById(cabinetBox);

        //
        CabinetBoxLog boxLog = new CabinetBoxLog();
        // 后续要跟全局ID统一
        boxLog.setLogId(IdWorker.getId());
        // 根据管理员所在校区获取他的 school_id
        boxLog.setSchoolId(1L);
        boxLog.setDevSn(oldBox.getDevSn());
        boxLog.setBoxId(oldBox.getBoxId());
        // 使用类型
        boxLog.setUseType(UseTypeEnum.OPERATION.code());
        boxLog.setCreateTime(new Date());
        boxLog.setBoxNo(oldBox.getBoxNo());

        // 修改完毕状态后应该记录日志
        int logCount = cabinetBoxLogMapper.insert(boxLog);

        // 重新统计主柜门的信息
        int count = cabinetMapper.reStatisticsBox(oldBox.getDevSn());
        return boxCount > 0;
    }

    @Override
    public CabinetBox view(String boxId) {
        CabinetBox cabinetBox = cabinetBoxMapper.selectById(boxId);
        return cabinetBox;
    }


    /**
     * 修改外卖柜实时情况
     * @param devSn 设备ID
     * @param canUse 可用
     * @param use 占用
     * @return
     */
    private Boolean updateCabinetState(String devSn,Integer canUse,Integer use){
        // 设置外卖柜整体情况
        Cabinet cabinet = cabinetMapper.selectById(devSn);
        // 可用
        cabinet.setCanUserBox(cabinet.getCanUserBox()+canUse);
        // 占用
        cabinet.setUseBox(cabinet.getUseBox()+use);
        int i = cabinetMapper.updateById(cabinet);
        return i > 0;
    }
}
